using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Diagnostics;

namespace TelnetExpect
{
    /// <summary>
    /// Allows the peer to enable <see cref="F:TelnetExpect.TelnetOption.EncryptionOption"/>
    /// by negotiation, and when the peer offers its list of encryption types it
    /// logs the contents of list, and refuses the offer.
    /// </summary>
    public class EncryptionNullTelnetHandler : TelnetOptionHandler
    {
        /// <summary>
        /// Initializes an instance of class <see cref="T:EncryptionNullTelnetHandler"/>.
        /// </summary>
        public EncryptionNullTelnetHandler()
        {
        }

        /// <exclude/>
        protected internal override void Init()
        {
            base.Init();
            Telnet.SetLocalMode(TelnetOption.EncryptionOption, true);
        }

        /// <exclude/>
        protected override void HandleSubNegotiation(byte[] data)
        {
            TelnetOption id = (TelnetOption)data[0];
            if (id == TelnetOption.EncryptionOption) {
                DumpEncrSubNeg(data, "Rcvd");
                Debug.Assert(Telnet.GetLocalMode(TelnetOption.EncryptionOption), "NOT Option Allowed locally");
                Debug.Assert(Telnet.GetLocalState(TelnetOption.EncryptionOption), "NOT Option Enabled locally");
                if (data.Length >= 2) {
                    EncrCmd ac = (EncrCmd)data[1];
                    switch (ac) {
                        case EncrCmd.SUPPORT:
                            HandleSupportCommand(data);
                            return;
                        default: // Shouldn't be any others.
                            TraceWriteLine("Ignoring Encryption option; unhandled command-code.");
                            break;
                    }
                } else {
                    TraceWriteLine("Ignoring Encryption option; is too short.");
                }
            }
        }

        private void HandleSupportCommand(byte[] data)
        {
            List<EncrType> list = ParseEncrSupportCommand(data);
            DumpEncrTypeList(list);
            EncrType? chosen = null;
            //foreach (EncrType cur in list) {
            if (chosen == null) {
                byte[] nullSelection = {
                        (byte)TelnetOption.EncryptionOption,
                        (byte)EncrCmd.IS,
                        (byte)EncrType.NULL,
                    };
                DumpEncrSubNeg(nullSelection, "Send");
                AddResponseSubNegotiation(nullSelection);
                return;
            }
            Debug.Fail("Unhandled 'Support' instance.");
        }

        private void DumpEncrTypeList(List<EncrType> list)
        {
            const string Sepa = "; ";
            StringBuilder bldr = new StringBuilder();
            foreach (EncrType cur in list) {
                bldr.AppendFormat(System.Globalization.CultureInfo.InvariantCulture,
                    "'{0}'", cur);
                bldr.Append(Sepa);
            }
            if (bldr.Length == 0) {
                bldr.Append("<empty list>");
            } else {
                bldr.Length -= Sepa.Length;
            }
            TraceWriteLine("encryption-type-list: [ {0} ].", bldr.ToString());
        }

        private List<EncrType> ParseEncrSupportCommand(byte[] data)
        {
            Debug.Assert(data.Length >= 2, "need two (option code and command) bytes -- should have been checked by caller!");
            Debug.Assert((TelnetOption)data[0] == TelnetOption.EncryptionOption);
            Debug.Assert((EncrCmd)data[1] == EncrCmd.SUPPORT);
            int offset = 2;
            List<EncrType> list = new List<EncrType>();
            TraceWriteLineIf(data.Length < 3, "*Empty* encryption-type-list received!");
            while (true) {
                // Check this first, to allow *empty* encr-type-list!!!
                if (data.Length - offset < 1) {
                    break;
                }
                byte encrType0 = data[offset + 0];
                EncrType encrType = (EncrType)encrType0;
                list.Add(encrType);
                offset += 1;
            }//while
            return list;
        }

        private void DumpEncrSubNeg(byte[] data, string prefix)
        {
            EncrCmd? ec = null;
            EncrType? et = null;
            //NtlmCmd? cmd = null;
            if (data.Length >= 2) {
                ec = (EncrCmd)data[1];
                if (data.Length >= 3) {
                    et = (EncrType)data[2];
                    //if (data.Length >= 4) {
                    //    am = (AuthModifiers)data[3];
                    //    if (data.Length >= 5) {
                    //        cmd = (NtlmCmd)data[4];
                    //    }
                    //}
                }
            }
            TraceWriteLine("{0} OptionEncr: Cmd: {1}, Type: {2}, leftover: {3}",
                prefix, ec, et, data.Length - 3/*, cmd*/);
        }

        enum EncrCmd
        {
            IS = 0,
            SUPPORT = 1,
            REPLY = 2,
            START = 3,
            REQUEST_START = 5,
            REQUEST_END = 6,
            ENC_KEYID = 7,
            DEC_KEYID = 8,
        }

        enum EncrType
        {
            NULL = 0,
            DES_CFB64 = 1,
            DES_OFB64 = 2,
            DES3_CFB64 = 3,
            DES3_OFB64 = 4,
            CAST5_40_CFB64 = 8,
            CAST5_40_OFB64 = 9,
            CAST128_CFB64 = 10,
            CAST128_OFB64 = 11,
            AES_CCM = 12, // [Josefsson]
        }

    }
}
